#include "Graphics/OpenGL/GLRenderEngine.h"
#include "Framework/StableHeaders.h"

#include "OS/OpenGL/GLWin32WindowManager.h"
#include "Graphics/OpenGL/GLDeviceBufferManager.h"
#include "Graphics/IViewport.h"
#include "Graphics/VertexElement.h"
#include "Graphics/OpenGL/GLDeviceIndexBuffer.h"
#include "Graphics/IDeviceVertexBuffer.h"
#include "Graphics/VertexDeclaration.h"
#include "Graphics/IRenderOperation.h"
#include "Graphics/VertexData.h"
#include "Graphics/VertexBufferBinding.h"
#include "Graphics/VertexIndexData.h"


namespace tyro
{
	namespace Graphics
	{

		// Convenience macro from ARB_vertex_buffer_object spec
		#define VBO_BUFFER_OFFSET(i) ((char *)NULL + (i))


		GLRenderEngine::GLRenderEngine()
			: IRenderEngine()
		{
			viewMatrix = Math::Matrix4f::IDENTITY;
			worldMatrix = Math::Matrix4f::IDENTITY;
		}

		GLRenderEngine::~GLRenderEngine()
		{

		}

		void GLRenderEngine::Update()
		{

			windowManager->Update();


			SetViewPort(activeViewport);

			activeViewport->Update();

			renderWindow->Update();
		}

		IRenderWindow* GLRenderEngine::Initialise( const char* const title )
		{
			runLog->Print("Initializing Window Manager\r\n");
			windowManager = new OS::GLWin32WindowManager();

			renderWindow = windowManager->CreateRenderWindow(title, 800, 600, false);

			AttachRenderTarget(*renderWindow);

			InitDeviceCapabilities();

			runLog->Print("Creating Viewport\r\n");
			activeViewport = new Graphics::IViewport( NULL ,renderWindow, 0, 0, 800,600);

			return renderWindow;
		}

		void GLRenderEngine::BeginFrame( void )
		{
			glEnable(GL_SCISSOR_TEST);
		}

		void GLRenderEngine::EndFrame( void )
		{
			glDisable(GL_SCISSOR_TEST);
		}

		

		void GLRenderEngine::ClearFrameBuffer( UINT32 bufferFlags, const Colour& colour /*= Colour::White*/, FLOAT_32 depth /*= 1.0f*/, UINT16 stencil /*= 0.0f*/ )
		{
			// Temp variable for buffer flags
			GLbitfield activeFlags = 0;

			// Test for colour buffer flag
			if (bufferFlags & FBT_COLOUR)
			{
				// Set the colour buffer flag for clearing
				activeFlags |= GL_COLOR_BUFFER_BIT;


				// Clear the frame buffers colour
				glClearColor(colour.red, colour.green, colour.blue, colour.alpha);
			}

			// Test for depth buffer flag
			if (bufferFlags & FBT_DEPTH)
			{
				// Set the depth buffer flag for clearing
				activeFlags |= GL_DEPTH_BUFFER_BIT;

				// Clear the depth buffer
				glClearDepth(depth);

				// Depth testing TODO move to pass
				glEnable(GL_DEPTH_TEST);
				glDepthFunc(GL_LEQUAL);
			}


			// Test for stencil buffer flag
			if (bufferFlags & FBT_STENCIL)
			{
				// Set the stencil buffer flag for clearing
				activeFlags |= GL_STENCIL_BUFFER_BIT;


				// Set the index for clearing
				glClearStencil(stencil);
			}


			// Clear active buffers
			glClear(activeFlags);
		}

		void GLRenderEngine::SetPolygonMode( PolygonMode polygonMode )
		{
			switch(polygonMode)
			{
			case PM_POINTS:
					 glPolygonMode(GL_FRONT_AND_BACK, GL_POINT);
					 break;
			case PM_WIREFRAME:
					 glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
					 break;
			case PM_FILL:
					 glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
					 break;
			}
		}

		void GLRenderEngine::SetProjectionMatrix( const Math::Matrix4f& matrix )
		{

			GLfloat mat[16];
			MakeGLMatrix(mat, matrix);
			glMatrixMode(GL_PROJECTION);
			glLoadMatrixf(mat);
			glMatrixMode(GL_MODELVIEW);

		}

		void GLRenderEngine::SetViewMatrix( const Math::Matrix4f& matrix )
		{
			viewMatrix = matrix;
			GLfloat mat[16];
			MakeGLMatrix( mat, viewMatrix * worldMatrix );
			glMatrixMode(GL_MODELVIEW);							
			glLoadMatrixf(mat);
		}

		void GLRenderEngine::SetWorldMatrix( const Math::Matrix4f& matrix )
		{
			worldMatrix = matrix;
			GLfloat mat[16];
			MakeGLMatrix( mat, viewMatrix * worldMatrix );
			glMatrixMode(GL_MODELVIEW);
			glLoadMatrixf( mat);
		}

		void GLRenderEngine::SetDepthBuffer( bool depthTest, bool depthMask )
		{
			SetDepthBufferTest(depthTest);
			SetDepthBufferMask(depthMask);
		}

		void GLRenderEngine::SetDepthBufferTest( bool enabled )
		{
			if (enabled)
			{
				glClear(1.0f);
				glEnable(GL_DEPTH_TEST);
			}
			else
			{
				glDisable(GL_DEPTH_TEST);
			}
		}

		void GLRenderEngine::SetDepthBufferMask( bool enabled )
		{
			GLboolean flag = enabled ? GL_TRUE : GL_FALSE;
			glDepthMask(flag);
		}

		void GLRenderEngine::SetViewPort( IViewport* viewport )
		{
			if (!viewport)
			{
				activeViewport = NULL;
				SetRenderTarget(NULL);
			}
			else if (viewport != activeViewport)
			{
				IRenderTarget* renderTarget;
				renderTarget = viewport->GetRenderTarget();
				SetRenderTarget(renderTarget);

				activeViewport = viewport;
				
				GLsizei x, y, w, h;

				w = viewport->GetActWidth();
				h = viewport->GetActHeight();
				x = viewport->GetActLeft();
				y = viewport->GetActTop();

				glViewport(x,y,w,h);

				glScissor(x, y, w, h);
			}
		}

		void GLRenderEngine::SetRenderTarget( IRenderTarget* renderTarget )
		{
			this->activeRenderTarget = renderTarget;
		}

		void GLRenderEngine::SetViewport( IViewport *vp )
		{
			if(!vp)
			{
				activeViewport = NULL;
			}
			else if (vp != activeViewport)
			{
				IRenderTarget* target = vp->GetRenderTarget();
				SetRenderTarget(target);
				activeViewport = vp;

				GLsizei x, y, w, h;

				// Calculate the "lower-left" corner of the viewport
				w = vp->GetActWidth();
				h = vp->GetActHeight();
				x = vp->GetActLeft();
				y = vp->GetActTop();

				glViewport(x, y, w, h);

				// Configure the viewport clipping
				glScissor(x, y, w, h);

			}


		}

		void GLRenderEngine::DrawTriangle()
		{
			glBegin(GL_TRIANGLES);
			glColor3f(0.1, 0.2, 0.3);
			glVertex3f(0, 0, 0);
			glVertex3f(1, 0, 0);
			glVertex3f(0, 1, 0);
			glEnd();

		}

		void GLRenderEngine::MakeGLMatrix( GLfloat gl_mat[16], const Math::Matrix4f& matrix )
		{
			size_t x = 0;
			for (size_t i = 0; i < 4; i++)
			{
				for (size_t j = 0; j < 4; j++)
				{
					gl_mat[x] = matrix.m[j][i];
					x++;
				}
			}
		}

		void GLRenderEngine::RenderOperation( const IRenderOperation& op )
		{
			IRenderEngine::RenderOperation(op);


			const VertexDeclaration::VertexElementList& decl = op.vertexData->vertexDeclaration->GetElements();

			VertexDeclaration::VertexElementList::const_iterator elemItr, elemEnd;
			elemEnd = decl.end();

			size_t maxSource = 0;

			for(elemItr = decl.begin(); elemItr != elemEnd; ++elemItr)
			{
				const VertexElement& elem = *elemItr;
				size_t source = elem.GetSource();
				if (maxSource < source)
				{
					maxSource = source;
				}

				
				if(!op.vertexData->vertexBufferBinding->IsBufferBound(source))
					continue;

				IDeviceVertexBufferSharedPtr vertexBuffer = op.vertexData->vertexBufferBinding->GetBuffer(source);


				BlindVertexElementToDevice(elem, vertexBuffer, op.vertexData->vertexStart);

			}

			glClientActiveTextureARB(GL_TEXTURE0);


			

			GLint primType;
			bool useAdjacency = false;
			switch (op.renderType)
			{
			case RT_POINT_LIST:
				primType = GL_POINTS;
				break;
			case RT_LINE_LIST:
				primType = useAdjacency ? GL_LINES_ADJACENCY_EXT : GL_LINES;
				break;
			case RT_LINE_STRIP:
				primType = useAdjacency ? GL_LINE_STRIP_ADJACENCY_EXT : GL_LINE_STRIP;
				break;
			default:
			case RT_TRIANGLE_LIST:
				primType = useAdjacency ? GL_TRIANGLES_ADJACENCY_EXT : GL_TRIANGLES;
				break;
			case RT_TRIANGLE_STRIP:
				primType = useAdjacency ? GL_TRIANGLE_STRIP_ADJACENCY_EXT : GL_TRIANGLE_STRIP;
				break;
			case RT_TRIANGLE_FAN:
				primType = GL_TRIANGLE_FAN;
				break;
			}




			if (op.useIndicies)
			{
				void* bufferData = 0;
				glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, 
					static_cast<GLDeviceIndexBuffer*>(
					op.indexData->indexBuffer.Get())->GetGLBufferID());

				bufferData = VBO_BUFFER_OFFSET(
					op.indexData->indexStart * op.indexData->indexBuffer->GetIndexSize());


				GLenum indexType = (op.indexData->indexBuffer->GetType() == VIT_16BIT) ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT;

				glDrawElements(primType, op.indexData->indexCount, indexType, bufferData);

			}
			

			glDisableClientState( GL_VERTEX_ARRAY );

			for (int i = 0; i < 8; i++)
			{
				glClientActiveTextureARB(GL_TEXTURE0 + i);
				glDisableClientState( GL_TEXTURE_COORD_ARRAY );
			}
			glClientActiveTextureARB(GL_TEXTURE0);

			glDisableClientState( GL_NORMAL_ARRAY );
			glDisableClientState( GL_COLOR_ARRAY );
			glDisableClientState( GL_SECONDARY_COLOR_ARRAY );

			



			glColor4f(1,1,1,1);
			glSecondaryColor3fEXT(0.0f, 0.0f, 0.0f);

			


		}

		void GLRenderEngine::InitDeviceCapabilities()
		{
			bufferManager = new Graphics::GLDeviceBufferManager;	
		}

		void GLRenderEngine::BlindVertexElementToDevice( const VertexElement& elem, IDeviceVertexBufferSharedPtr vertexBuffer, size_t vertexStart )
		{

			void* bufferData = 0;


			


			glBindBufferARB(GL_ARRAY_BUFFER_ARB, 1);
			bufferData = VBO_BUFFER_OFFSET(elem.GetMemOffset());



			VertexElementSemantic sem = elem.GetSemantic();

			


			switch(sem)
			{
			case VES_POSITION:
				glVertexPointer(VertexElement::GetTypeCount(elem.GetType()),
								GLDeviceBufferManager::GetGLType(elem.GetType()),
								static_cast<GLsizei>(vertexBuffer->GetVertexSize()),
								bufferData);
				glEnableClientState(GL_VERTEX_ARRAY);
				break;
			case VES_NORMAL:
				glNormalPointer(GLDeviceBufferManager::GetGLType(elem.GetType()),
								static_cast<GLsizei>(vertexBuffer->GetVertexSize()),
								bufferData);
				glEnableClientState(GL_NORMAL_ARRAY);
				break;
			case VES_DIFFUSE:
				break;
			case VES_SPECULAR:
				break;
			case VES_TEXTURE_COORDINATES:

				break;
			default:
				break;
			};
		}

		void GLRenderEngine::SetNormaliseNormals( bool enabled )
		{
			if (enabled)
			{
				glEnable(GL_NORMALIZE);
			} 
			else
			{
				glDisable(GL_NORMALIZE);
			}
		}

		void GLRenderEngine::SetColour( Graphics::Colour colour )
		{
			glColor3f(colour.red, colour.green, colour.blue);
		}

		void GLRenderEngine::translate( Math::Vector3f vec )
		{
			glTranslatef(vec.x, vec.y, vec.z);
		}

		void GLRenderEngine::SetWorldMatrices( const Math::Matrix4f* matrices, USHORT_16 numMatrices )
		{
			for (int i = 0; i < numMatrices; i++)
			{
				SetWorldMatrix(matrices[i]);
			}
		}

		void GLRenderEngine::SetCullingMode( CullingMode mode )
		{
			// Store the active culling mode
			activeCullingMode = mode;

			GLenum cullingMode;

			switch(mode)
			{
			case CULL_NONE:
				glDisable( GL_CULL_FACE );
				return;
			case CULL_ANTICLOCKWISE:

				cullingMode = GL_FRONT;
				break;
			case CULL_CLOCKWISE:

				cullingMode = GL_BACK;
				break;
			}

			// Set the active culling mode
			glEnable(GL_CULL_FACE);
			glCullFace(cullingMode);
		}

		void GLRenderEngine::SetSurfaceParams( const Colour &ambient, const Colour &diffuse, const Colour &specular, const Colour &emissive, FLOAT_32 shininess )
		{

			glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT);
			glEnable(GL_COLOR_MATERIAL);

			GLfloat f4val[4] = {diffuse.red, diffuse.green, diffuse.blue, diffuse.alpha};
			glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, f4val);
			f4val[0] = ambient.red;
			f4val[1] = ambient.green;
			f4val[2] = ambient.blue;
			f4val[3] = ambient.alpha;
			glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, f4val);
			f4val[0] = specular.red;
			f4val[1] = specular.green;
			f4val[2] = specular.blue;
			f4val[3] = specular.alpha;
			glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, f4val);
			f4val[0] = emissive.red;
			f4val[1] = emissive.green;
			f4val[2] = emissive.blue;
			f4val[3] = emissive.alpha;
			glMaterialfv(GL_FRONT_AND_BACK, GL_EMISSION, f4val);
			glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, shininess);
		}

		void GLRenderEngine::SetLightingActive( bool enabled )
		{
			if (enabled) 
			{      
				glEnable(GL_LIGHTING);
			} 
			else 
			{
				glDisable(GL_LIGHTING);
			}
		}


		void GLRenderEngine::SetAmbientLight( FLOAT_32 r, FLOAT_32 g, FLOAT_32 b )
		{
			glLightf( GL_LIGHT0, GL_SPOT_CUTOFF, 178.0f );
			GLfloat f4vals[4] = {1, 1, 1, 1};
			glLightfv(GL_LIGHT0, GL_DIFFUSE, f4vals);
			f4vals[0] = 0;
			f4vals[1] = 0;
			f4vals[2] = 0;
			f4vals[3] = 1;
			glLightfv(GL_LIGHT0, GL_SPECULAR, f4vals);
			glLightfv(GL_LIGHT0, GL_AMBIENT, f4vals);

			Math::Vector4f vec(20, 80, 50, 1);
			glLightfv(GL_LIGHT0, GL_SPOT_DIRECTION, vec);

			glLightf(GL_LIGHT0, GL_CONSTANT_ATTENUATION, 1);
			glLightf(GL_LIGHT0, GL_LINEAR_ATTENUATION, 0);
			glLightf(GL_LIGHT0, GL_QUADRATIC_ATTENUATION, 0);

			glEnable(GL_LIGHT0);

			GLfloat ambientLight[] = { r, g, b, 1.0f};
			glLightModelfv(GL_LIGHT_MODEL_AMBIENT, ambientLight);
		}

	}
}